import java.util.Collections
import java.util.HashMap
import model.*
import util.*
import java.util.ArrayList
import game.Player
import game.PlayerState
import com.sun.swing.internal.plaf.synth.resources.synth


class TurnState(var actions: Int = 1, var buys: Int = 1, var coins: Int = 0)


trait GameState {
    fun getKingdomCards(): Map<ActionCard, Int>
    fun getVictoryCards(): Map<VictoryCard, Int>
    fun getTreasureCards(): Map<TreasureCard, Int>
    fun getDecks(): Map<String, Boolean>
    fun getDiscardTop(): Map<String, Card?>
    fun isFinished(): Boolean
}

class Game(vararg name: String) {
    {
        if (name.size !in 2..4) throw IllegalArgumentException("Only 2 to 4 players supported")
    }
    val players = name.map { Player(it) }
    val playerMap = players.mapBy { it.name }
    var playerIterator = players.iterator()
    val victoryCardsNumber = if (name.size == 2) 8 else 12


    val shuffledKingdomCards = starterKingdomCards
    //allKingdomCards.shuffledCopy()

    val kingdom = hashMapOf(
            estate to Deck.openDeck(victoryCardsNumber, { estate }),
            duchy to Deck.openDeck(victoryCardsNumber, { duchy }),
            province to Deck.openDeck(victoryCardsNumber, { province }),
            cooper to Deck.openDeck(60, { cooper }),
            silver to Deck.openDeck(40, { silver }),
            gold to Deck.openDeck(30, { gold })
    );
    {
        for (i in 0..9) {
            kingdom.put(shuffledKingdomCards[i], Deck.openDeck(10, { shuffledKingdomCards[i] }))
        }
        initNextPlayer()
    }

    fun isFinished(): Boolean {
        if (kingdom[province]!!.size() == 0) return true
        return kingdom.values().fold(0, { z, deck -> if (deck.size() == 0) z + 1 else z }) > 2
    }

    fun initNextPlayer() {
        if (!playerIterator.hasNext()) playerIterator = players.iterator()
        val player = playerIterator.next()
        player.state = PlayerState.ACTING
        player.turns++
        player.actions = 1
        player.buys = 1
        player.coins = 0
        player.menu = player.actionMenu()
    }

    fun getLeader(): Player = players.max { deck.victoryPoints() }
    fun play(playerName: String, selection: List<Card>) = playerMap[playerName]?.play(selection)
    fun getCurrentMenu(playerName: String) = playerMap[playerName]?.getCurrentMenu()
    fun getHand(playerName: String) = playerMap[playerName]?.hand.orEmpty()


    fun Player.applyCardExtras(actionCard: ActionCard) {
        actionCard.extraCard times { hand.safeAdd(nextCard()) }
        actions += actionCard.extraAction
        coins += actionCard.extraCoins
        buys += actionCard.extraBuy
    }

    fun Player.flushPlayingArea() {
        playingArea.forEach { discard.push(it) }
        playingArea.clear()
    }

    fun Player.startBuying() {
        flushPlayingArea()
        state = PlayerState.BUYING
        actionState = null
        menu = buyMenu()
    }

    fun Player.continueActing() {
        flushPlayingArea()
        actionState = null
        menu = actionMenu()
    }

    fun Player.play(selection: List<Card>) {
        val previousMenu = this.menu
        if (previousMenu == null || !previousMenu.validate(selection)) return
        val previousActionState = actionState
        when (state) {
            PlayerState.WAITING -> return
            PlayerState.ACTING -> {
                if (previousActionState == null) {
                    val selectedCard = selection.firstOrNull()
                    when (selectedCard) {
                        null -> startBuying()
                        is ActionCard -> {
                            if (hand.remove(selectedCard)) {
                                playingArea.add(selectedCard)
                                actions--
                                applyCardExtras(selectedCard)
                                val nextActionState = selectedCard.createActionState()
                                val nextMenu = nextActionState.getMenu(this@Game, this)
                                if (nextMenu == null) {
                                    continueActing()
                                } else {
                                    actionState = nextActionState
                                    menu = nextMenu
                                }
                            }
                        }
                        else -> { }
                    }
                } else {
                    previousActionState.apply(this@Game, this, selection)
                    if (previousActionState.isFinished()) {
                        continueActing()
                    }
                    menu = previousActionState.getMenu(this@Game, this)
                }
            }
            PlayerState.BUYING -> {
                if (selection.isEmpty()) {
                    this.menu = null
                    hand.forEach { discard.push(it) }
                    hand.clear()
                    5 times { hand.safeAdd(nextCard()) }
                    initNextPlayer()
                    return
                }
                val buy = selection.get(0)
                if (buys > 0 && amountAvailable() >= buy.cost) {
                    val deck = kingdom[buy]!!
                    val bought = deck.pop()
                    if (bought != null) {
                        buys--
                        coins -= buy.cost
                        discard.push(buy)
                        this.menu = buyMenu()
                    }
                }
            }
        }
    }

    fun Player.buyMenu(): Menu {
        val availableToBuy = ArrayList<Card>()
        val amountAvailable = amountAvailable()
        kingdom.values().forEach {
            val card = it.peek()
            if (card != null && card.cost <= amountAvailable) availableToBuy.add(card)
        }
        return Menu("Select card to buy ($amountAvailable coins, $buys buys): ", availableToBuy, true)
    }

    fun Player.actionMenu() = Menu("Select card to act (${actions} actions):", hand.filter { it is ActionCard }, true)

}
